Animations can be very powerful tools when used effectively, allowing you to see progression over time, iterate through plots, and help you tell a better story with the data. This guide will give an overview of packages, techniques, and tips for creating animated plots in R.
In this guide you will learn:
To create animations, there are some R packages you will need to install & load first. The main packages we will be using are:
You will likely need to install the following packages if you haven’t worked with animations before:
install.packages("gganimate")
install.packages("gapminder")
install.packages("transformr")
Then load the necessary packages:
library(gganimate)
library(ggplot2)
library(tidyverse)
library(gapminder)
library(lubridate)
To create animated plots we must first have some tidy data to work with. We’ll start with an unique data set to get familiar with some of the basic features and then later explore some example data.
To Do:
One you have a graph of multiple terms, download this as a .csv file to your computer.
Note: Once you have your .csv downloaded, click on it in the bottom right “Files” pane and click “View File”. The .csv file will open in another tab. Delete the first two rows so that “Week” and the terms you searched are now at the top. Change the column names to something short and simple. Click “Save”, and now your .csv is ready for importing. Save it to the same folder as this file.
popularity <- read_csv("multiTimeline.csv")
popularity$Week <- mdy(popularity$Week)
head(popularity)
popularity <- read_csv("/Users/nolan/MACALESTER/Stat 456/multiTimeline.csv")
head(popularity)
## # A tibble: 6 × 4
## Week `World cup` `Premier League` `UEFA Champions League`
## <date> <dbl> <dbl> <dbl>
## 1 2017-09-24 1 5 7
## 2 2017-10-01 3 3 1
## 3 2017-10-08 4 3 1
## 4 2017-10-15 1 4 7
## 5 2017-10-22 2 5 1
## 6 2017-10-29 1 4 8
We want this data in tidy format, so lets pivot_longer our data.
popularity <- popularity %>%
pivot_longer(cols = `*first column*`:`*last column*`,
names_to = "Search Term",
values_to = "Popularity")
popularity <- popularity %>%
pivot_longer(cols = `World cup`:`UEFA Champions League`,
names_to = "Search Term",
values_to = "Popularity")
Now make a line plot the popularity over time:
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity") +
theme_classic() +
theme(legend.position = "top")
Now we’re ready for animations.
Within gganimate, the main concept is the use of transition functions to create an animation. Other components of an animation the we will cover along with these functions are:
We will walk through each of these in detail and build upon them to create some cool visualizations.
Transitions control how the data gets displayed in your animation. Do you want your data to populate over time by year, by category, or another filter? This is determined by the transition function you select. The list of common transition functions is shown below, along with their functionality.
| Name | Functionality |
|---|---|
| transition_reveal | Reveal data along a given dimension |
| transition_filter | Transition between different filters |
| transition_states | Transition between several distinct stages of the data |
| transition_time | Transition through distinct states in time |
| transition_layers | Build up a plot, layer by layer |
Transition functions are simply added to your regular ggplot statements following a “+”.
Transition functions also have a few common arguments that are useful to know as you often will want to adjust these. Typically, whatever variable you are animating on goes first. In addition, the other main arguments are:
| Name | Functionality |
|---|---|
| transition_length | Controls the relative length of individual transitions |
| filter/layer/state_length | Controls the relative length of pause at each state |
| range | Controls the time range to animate (if applicable) |
| wrap | Controls if the animation should wrap-around back to the start at the end |
There are of course other arguments specific to each transition function, however these are the crucial ones to know when building animations.
Lets try using transition_reveal() first. This transition will reveal the data along a given dimension – in this case time. This can be a good option when you have line charts or time-series data and want to show a progression of data over that dimension.
To Do:
Add transition_reveal() to your plot, with the Week variable as the first argument.
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity") +
theme_classic() +
theme(legend.position = "top") +
???
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity") +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week)
You should now see your plot animated in the viewer in the bottom right panel of your screen!
Another thing we should make sure to add to our animation is how it is transitioning. While we know the animation is changing by week, our viewer may not realize or understand that. With some transition functions this may or may not be easily inferable, so it’s good practice to include this information on the plot.
We can add this information in the labs() part of our ggplot statement, specifying one of the corresponding transition variables related to the transition function we are using (see below).
| Name | Label variable(s) |
|---|---|
| transition_reveal | frame_along |
| transition_filter | previous_filter, closest_filter, next_filter |
| transition_states | previous_state, closest_state, next_state |
| transition_time | frame_time |
| transition_layers | previous_layer, closest_layer, next_layer, nlayers |
To Do:
In this case, lets add title = “Week: {frame_along}” to our label.
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", ???) +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week)
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week)
Now we can see each week at the top as it is being displayed.
Another thing we can alter about the animation is how the scales change. Currently they are static, which may not be the best when we have spread out data like this. You can change how the animation is viewed via view functions. The main one is:
You can also fix the x or y axis separately if it makes sense (it does in this case as we have values on a fixed scale from 0-100), using the fixed_x and fixed_y arguments.
To Do:
Try adding view_follow(fixed_y = TRUE) to the previous animation as see how it looks.
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week) +
???
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week) +
view_follow(fixed_y = TRUE)
This is a different way to show data populating over time and can be useful when the data is pretty spread out.
We want to be able to save and display our animations in a good format for viewing. Saving animations can save space and time to load them, and adjusting features like duration can also greatly improve the final animation. There are a few functions that help with that.
To Do:
To save animations as gifs you can use the anim_save() function, imputing the desired file name in the parentheses. Then, to load it use the knitr::include_graphics() function, with the file name in the parentheses. Try it out on a previous plot.
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week)
anim_save("popularity_anim")
knitr::include_graphics("popularity_anim")
To adjust how animations are displayed, including duration, height/width, fps, and more, the animate() function is useful. Key arguments that can be changed are:
To Do:
Save your plot and then try adjusting some of these (like the ‘fps’ setting)to see how the output of the animation changes.
p <- popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
theme_classic() +
theme(legend.position = "top") +
transition_reveal(Week)
animate(p, width = 700, height = 425, fps = 25, duration = 16, rewind = FALSE, start_pause = 25, end_pause = 25)
These adjustments can greatly improve how the animation is displayed, but note that it can slow down processing time.
Now, perhaps we wanted to iterate through the different search terms, rather than seeing them all at once. In this case, we can use transition_filter(). This transition allows you to animate trough a range of filtering conditions – in this case Search Term – and see the data separately for each. This can be a cleaner way to view multiple categories of data, but makes comparison between them harder.
To Do:
Using the same plot as before, change transition_reveal() to transition_filter(). Then, add in the following arguments: transition_length, filter_length, and the filter conditions. These conditions should be the Search Term equal to each of your terms. Also, update the title label to the corresponding variable: “{closest_filter}”. See below for reference:
Note: you might need to install the ‘transformr’ package.
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "{closest_filter}") +
theme_classic() +
theme(legend.position = "top") +
transition_filter(transition_length = 0.05,
filter_length = 2, `Search Term` == "var1", `Search Term` == "var2", `Search Term` == "var3")
popularity %>%
ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
geom_line() +
labs(x = "Time", y = "Popularity", title = "{closest_filter}") +
theme_classic() +
theme(legend.position = "top") +
transition_filter(transition_length = 0.05,
filter_length = 2,
`Search Term` == "Premier League", `Search Term` == "UEFA Champions League", `Search Term` == "World cup")
When we run the plot we now get the animation transitioning through each of these filters, providing a separate view of each of the search terms’ popularity over time.
Another transition function is transition_states(). This function allows you to animate between several distinct stages of the data, whether that be before/during/after an event, by time segment, or another relevant partition.
To Do:
Before we use this, lets focus our data down to a smaller section of time for one category. Filter your data to include only one term for the most recent 4-6 weeks of data, like so:
recent_term <- popularity %>%
filter(Week > "2022-08-01",
`Search Term` == "var1")
recent_term <- popularity %>%
filter(Week > "2022-08-01",
`Search Term` == "Premier League")
Now, create a bar chart of this data, with Week on the x-axis and Popularity on the y-axis. Coloring by Popularity can also make this plot more visually appealing, but is not necessary.
recent_term %>%
ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
geom_col() +
scale_fill_distiller(palette = "Greens", direction = 1) +
theme_classic()
From here, we can add in the transition_states() function, as we have distinct stages (weeks) of data. Use the Week variable as the first argument in the transition function, and don’t forget to add a label for the transition! In this case you have a few options for the label, if you want to display the previous, current, on next state respectively.
recent_term %>%
ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
geom_col() +
scale_fill_distiller(palette = "Greens", direction = 1) +
theme_classic() +
labs(title = "Week: {closest_state}") +
transition_states(Week, wrap = FALSE)
The previous animation you made was pretty neat, but it only really provided useful comparison for successive weeks, and not across the whole time span. Wouldn’t it be great if there was a way to keep the previous values displayed during the animation? Well, there is – with shadows!
Shadows allow you to show previous data during the course of an animation. The three shadow functions are:
Each offer different ways of representing past data in an animation, as their names suggest.
To Do:
Try adding shadow_mark() to the previous bar chart animation and see what it does.
recent_term %>%
ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
geom_col() +
scale_fill_distiller(palette = "Greens", direction = 1) +
theme_classic() +
labs(title = "Week: {closest_state}") +
transition_states(Week, wrap = FALSE) +
???
recent_term %>%
ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
geom_col() +
scale_fill_distiller(palette = "Greens", direction = 1) +
theme_classic() +
labs(title = "Week: {closest_state}") +
transition_states(Week, wrap = FALSE) +
shadow_mark()
Now we can see the previous bars throughout the animation, which makes comparison across weeks a bit easier.
We will explore the other shadow functions later in other plots.
Some transition functions work better on different types of data. To showcase the other functions, we will switch to the gapminder data set. This is a great example data set for animations, and it contains data on worldwide economic, health, and other public information.
To Do:
Load the data set and preview it below:
head(gapminder)
## # A tibble: 6 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779.
## 2 Afghanistan Asia 1957 30.3 9240934 821.
## 3 Afghanistan Asia 1962 32.0 10267083 853.
## 4 Afghanistan Asia 1967 34.0 11537966 836.
## 5 Afghanistan Asia 1972 36.1 13079460 740.
## 6 Afghanistan Asia 1977 38.4 14880372 786.
We will start again by creating a plot using some of the 6 variables in the data set. Here’s an example:
gapminder %>%
ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
geom_point(alpha = 0.75, show.legend = FALSE) +
scale_color_viridis_d() +
labs(x = "GDP per capita", y = "Life expectancy") +
theme_classic()
Lets add a log transformation so we can see the data a bit better:
gapminder %>%
ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
geom_point(alpha = 0.75, show.legend = FALSE) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy") +
theme_classic()
In some cases, you may want to show multiple geoms on the same plot, like a line and bar chart together. Transition_layers() allows for this functionality, and adds these layers in one-by-one during the animation.
To Do:
First, lets edit the previous plot and add some more geom elements to it. Remove the coloring and size, and add a histogram of the GDP Per Capita and a geom_smooth of the points.
gapminder %>%
ggplot() +
geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
theme_classic()
Now, lets add transition_layer() to animate this plot.
gapminder %>%
ggplot() +
geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
theme_classic() +
???
gapminder %>%
ggplot() +
geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
theme_classic() +
transition_layers()
This can be a great way to show multiple types of plots layered together as seen here.
As the name implies, this function allows you to transition through distinct states in time, like year. This is a better option for point data, whereas transition_reveal() is better for line data.
To Do:
To see it in action, add transition_time() to the original gapminder plot, with year as the argument, and adding the correct label for this function.
gapminder %>%
ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
geom_point(alpha = 0.75, show.legend = FALSE) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy", title = "Year: ???") +
theme_classic() +
???
gapminder %>%
ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
geom_point(alpha = 0.75, show.legend = FALSE) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
theme_classic() +
transition_time(year)
Try adding another shadow function to this animation:
gapminder %>%
ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
geom_point(alpha = 0.75, show.legend = FALSE) +
scale_color_viridis_d() +
scale_x_log10() +
labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
theme_classic() +
transition_time(year) +
shadow_wake(wake_length = 0.2)